library(tidyverse)

Materials by Alison Presmanes Hill

Take A Sad Plot & Make It Better

https://apreshill.github.io/ohsu-biodatavis/slides.html#1

Quick example:

set.seed(1000)
asdpop <- tibble::tibble(
  time1 = sample(1:100, 100, replace = F), 
  time2 = time1) %>% 
  tidyr::gather(x, y, time1:time2, factor_key = TRUE)  
asdpop

Some edits:

asdpop <- asdpop %>% 
  mutate(services = as.factor(case_when(
    x == "time1" & y <= 30 ~ 1, 
    x == "time1" & y > 30 ~ 0, 
    x == "time2" & y <= 60 ~ 1, 
    TRUE ~ 0
    )))
asdpop

First shot

bar1 <- ggplot(asdpop, aes(x, fill = services)) 
bar1 <- bar1 + geom_bar(width = .6)
bar1

Simple, right?

  • Set colors of your preference:
library(wesanderson)
ff <- wes_palette("FantasticFox1")[c(2:3)]
bar2 <- bar1 + scale_fill_manual(values = ff)
bar2

  • Clean up a bit: change background, adjust axes, adjust ticks, etc
bar3 <- bar2 + scale_x_discrete(name = "", labels = c("Time 1", "Time 2"))
bar3 <- bar3 + scale_y_continuous(expand = c(.02, 0),
                                  name = "ASD Cases per 10,000") 
bar3 <- bar3 + theme_bw() 
bar3 <- bar3 + theme(axis.title = element_text(size = 10)) 
bar3 <- bar3 + theme(legend.text = element_text(size = 10)) 
bar3 <- bar3 + theme(legend.title = element_text(size = 10)) 
bar3 <- bar3 + theme(axis.ticks = element_blank())  
bar3 <- bar3 + theme(panel.border = element_blank())  
bar3 <- bar3 + theme(axis.line = element_blank()) 
bar3 <- bar3 + theme(panel.grid = element_blank())
bar3

  • Annotate as needed to deliver the message: do we need a legend?
bar4 <- bar3 + annotate("text", label = "Accessing \nServices", 
                        x = 2, y = 30, size = 4, color = "white", 
                        fontface = "bold") 
bar4 <- bar4 + annotate("text", label = "Not \nAccessing \nServices", 
                        x = 2, y = 80, size = 4, color = "white", 
                        fontface = "bold") 
bar4 <- bar4 + guides(fill = FALSE)
bar4

  • Keep delivering the message
# add the top horizontal line for population prevalence
bar5 <- bar4 + geom_segment(aes(x = .6, xend = 2.45, y = 100, yend = 100), 
                            lty = 3, lwd = .3, colour = "black")
bar5

bar6 <- bar5 + coord_cartesian(ylim = c(0, 102), xlim = c(1, 3.2)) 
bar6 <- bar6 + annotate("text", 
                        x = 2.5, y = 97, size = 4, hjust = 0, 
                        label = "Estimates of prevalence based\non population sampling will remain\nstable over time if true prevalence\nis stable.") 
bar6

  • Adding segments: emphasize change
# add segments to track sample prevalence
bar7 <- bar6 + geom_segment(aes(x = .6, xend = 1.3, y = 30, yend = 30), 
                            lty = 3, lwd = .5, colour = ff[2]) 
bar7 <- bar7 + geom_segment(aes(x = 1.3, xend = 1.7, y = 30, yend = 60), 
                            lty = 3, lwd = .5, colour = ff[2]) 
bar7 <- bar7 + geom_segment(aes(x = 1.7, xend = 2.45, y = 60, yend = 60), 
                            lty = 3, lwd = .5, colour = ff[2])
bar7

How were those specific numbers obtained?

asdpop %>% 
  group_by(services) %>% 
  summarize(minimum = min(y), 
            maximum = max(y))
  • New annotation
bar8 <- bar7 + annotate("text", 
                        x = 2.5, y = 60, size = 4, hjust = 0, 
                        label = "Estimates of prevalence based\non individuals accessing services\ncan create an illusion of an\nincrease in prevalence over time,\nyet still underestimate prevalence\nat both time points.")
bar8

  • Can we visualze how many actual observations are in each category?
set.seed(2018)
dot <- ggplot(asdpop, aes(x))
dot <- dot + geom_jitter(aes(y = y, colour = services), 
                             position = position_jitter(width = .25, 
                                                        height = 0), 
                             alpha = .75, size = 2) 
dot <- dot + scale_x_discrete(name = "", labels = c("Time 1", "Time 2"))
dot <- dot + scale_y_continuous(name = "ASD Cases per 10,000") 
dot <- dot + guides(colour = guide_legend(keyheight = 1.5))
dot

  • Like before, consider a cleaner look:
dotseg <- dot + scale_colour_manual(values = ff,
                                      name = "",
                                      labels = c("Not accessing \nservices",
                                                 "Accessing \nservices")) 
dotseg <- dotseg + annotate("text", 
                        x = 1.2, y = 102, size = 4, hjust = 0, 
                        label = "True ASD Prevalence")
dotseg <- dotseg + geom_segment(aes(x = .6, xend = 2.4, y = 100, yend = 100), 
                              lty = 3, lwd = .5, colour = "black") 
dotseg <- dotseg + geom_segment(aes(x = .6, xend = 1.3, y = 30, yend = 30), 
                              lty = 3, lwd = .5, colour = ff[2]) 
dotseg <- dotseg + geom_segment(aes(x = 1.3, xend = 1.7, y = 30, yend = 60), 
                              lty = 3, lwd = .5, colour = ff[2]) 
dotseg <- dotseg + geom_segment(aes(x = 1.7, xend = 2.4, y = 60, yend = 60), 
                              lty = 3, lwd = .5, colour = ff[2])
dotseg <- dotseg + theme(axis.ticks = element_blank()) 
dotseg

  • Deliver the message: highlight observations
set.seed(2018)
dotcol <- ggplot(asdpop, aes(x))
dotcol <- dotcol + geom_bar(fill = "white", width = .6)
dotcol <- dotcol + geom_jitter(aes(y = y, colour = services), 
                             position = position_jitter(width = .25, 
                                                        height = 0), 
                             alpha = .75, size = 2) 
dotcol <- dotcol + scale_x_discrete(name = "", labels = c("Time 1", "Time 2"))
dotcol <- dotcol + scale_y_continuous(name = "ASD Cases per 10,000") 
dotcol <- dotcol + scale_colour_manual(values = ff,
                                      name = "",
                                      labels = c("Not accessing \nservices",
                                                 "Accessing \nservices")) 
dotcol <- dotcol + guides(colour = guide_legend(keyheight = 1.5))
dotcol <- dotcol + annotate("text", 
                        x = 1.2, y = 102, size = 4, hjust = 0, 
                        label = "True ASD Prevalence")
dotcol <- dotcol + geom_segment(aes(x = .6, xend = 2.4, y = 100, yend = 100), 
                              lty = 3, lwd = .5, colour = "black") 
dotcol <- dotcol + geom_segment(aes(x = .6, xend = 1.3, y = 30, yend = 30), 
                              lty = 3, lwd = .5, colour = ff[2]) 
dotcol <- dotcol + geom_segment(aes(x = 1.3, xend = 1.7, y = 30, yend = 60), 
                              lty = 3, lwd = .5, colour = ff[2]) 
dotcol <- dotcol + geom_segment(aes(x = 1.7, xend = 2.4, y = 60, yend = 60), 
                              lty = 3, lwd = .5, colour = ff[2])
dotcol <- dotcol + theme(axis.ticks = element_blank()) 
dotcol <- dotcol + theme(legend.key=element_blank()) 
dotcol

  • Data/Ink ratio: cleaner look
set.seed(2018)
dotbw <- ggplot(asdpop, aes(x, y))
dotbw <- dotbw + geom_jitter(aes(colour = services), 
                             position = position_jitter(width = .25, 
                                                        height = 0), 
                             alpha = .75, size = 2) 
dotbw <- dotbw + scale_x_discrete(name = "", labels = c("Time 1", "Time 2"))
dotbw <- dotbw + scale_y_continuous(expand = c(.02, 0),
                                    name = "ASD Cases per 10,000") 
dotbw <- dotbw + scale_colour_manual(values = ff,
                                      name = "",
                                      labels = c("Not accessing \nservices",
                                                 "Accessing \nservices")) 
dotbw <- dotbw + guides(colour = guide_legend(keyheight = 1.5))
dotbw <- dotbw + theme_bw() 
dotbw <- dotbw + theme(axis.ticks = element_blank()) 
dotbw <- dotbw + theme(panel.border = element_blank()) 
dotbw <- dotbw + theme(panel.grid = element_blank()) 
dotbw <- dotbw + theme(axis.title.y = element_text(size = 10)) 
dotbw <- dotbw + theme(axis.text = element_text(size = 10))
dotbw <- dotbw + theme(axis.line = element_line(colour = "gray80"))
dotbw

  • How are Time 1 and Time 2 characterized?
set.seed(2018)
dotleg <- ggplot(asdpop, aes(x, y))
dotleg <- dotleg + geom_jitter(aes(colour = services), 
                             position = position_jitter(width = .25, 
                                                        height = 0), 
                             alpha = .75, size = 2) 
dotleg <- dotleg + scale_x_discrete(expand = c(0, 0.6),
                                    name = "", 
                                    labels = c("Time 1:\nPoor Service Access", "Time 2:\nBetter Service Access"))
dotleg <- dotleg + scale_y_continuous(expand = c(.02, 0),
                                      name = "ASD Cases per 10,000",
                                      breaks = seq(0, 100, by = 20)) 
dotleg <- dotleg + theme_bw() 
dotleg <- dotleg + theme(axis.ticks = element_blank()) 
dotleg <- dotleg + theme(panel.border = element_blank()) 
dotleg <- dotleg + theme(panel.grid = element_blank()) 
dotleg <- dotleg + theme(axis.title.y = element_text(size = 10)) 
dotleg <- dotleg + theme(axis.text = element_text(size = 10))
dotleg <- dotleg + coord_cartesian(ylim = c(0, 102), xlim = c(1, 3.2)) 
dotleg <- dotleg + scale_colour_manual(name = "ASD cases who are:", 
                                     values = ff, 
                                     labels = c("Not accessing services",
                                                "Accessing services")) 
dotleg <- dotleg + guides(colour = guide_legend(keywidth = 1.1, 
                                keyheight = 1.1, 
                                override.aes = list(alpha = 1, size = 3))) 
dotleg <- dotleg + theme(legend.position=c(.75, .25)) 
dotleg <- dotleg + theme(legend.text = element_text(size = 10)) 
dotleg <- dotleg + theme(legend.title = element_text(size = 10)) 
dotleg <- dotleg + theme(legend.background = element_rect(fill = "gray90", 
                                          size=.3, 
                                          linetype="dotted"))
dotleg

  • Bring back the segments to highlight slope:
# lines
dotline <- dotleg + geom_segment(aes(x = .6, xend = 2.4, y = 100, yend = 100), 
                              lty = 3, lwd = .5, colour = "black") 
dotline <- dotline + geom_segment(aes(x = .6, xend = 1.3, y = 30, yend = 30), 
                              lty = 3, lwd = .5, colour = ff[2]) 
dotline <- dotline + geom_segment(aes(x = 1.3, xend = 1.7, y = 30, yend = 60), 
                              lty = 3, lwd = .5, colour = ff[2]) 
dotline <- dotline + geom_segment(aes(x = 1.7, xend = 2.4, y = 60, yend = 60), 
                              lty = 3, lwd = .5, colour = ff[2])
dotline

  • Annotate
dotann <- dotline + annotate("text", 
                            x = 2.5, y = 97, size = 4, hjust = 0, 
                            label = "Estimates of prevalence based\non population sampling will remain\nstable over time if true prevalence\nis stable.")  
dotann <- dotann + annotate("text", 
                            x = 2.5, y = 60, size = 4, hjust = 0, 
                            label = "Estimates of prevalence based\non individuals accessing services\ncan create an illusion of an\nincrease in prevalence over time,\nyet still underestimate prevalence\nat both time points.") 
dotann

  • Putting many pieces together:
set.seed(2018)
dotprint <- ggplot(asdpop, aes(x, y))
dotprint <- dotprint + geom_jitter(aes(fill = services), 
                                   position = position_jitter(width=.25,
                                                              height = 0),
                                   pch = 21,
                                   colour = "black", 
                                   size = 2) 
dotprint <- dotprint + scale_x_discrete(expand = c(0, 0.6),
                                    name = "", 
                                    labels = c("Time 1:\nPoor Service Access", "Time 2:\nBetter Service Access"))
dotprint <- dotprint + scale_y_continuous(expand = c(.02, 0),
                                      name = "ASD Cases per 10,000",
                                      breaks = seq(0, 100, by = 20)) 
dotprint <- dotprint + theme_bw() 
dotprint <- dotprint + theme(axis.ticks = element_blank()) 
dotprint <- dotprint + theme(panel.border = element_blank()) 
dotprint <- dotprint + theme(panel.grid = element_blank()) 
dotprint <- dotprint + theme(axis.title.y = element_text(size = 10)) 
dotprint <- dotprint + theme(axis.text = element_text(size = 10))
dotprint <- dotprint + coord_cartesian(ylim = c(0, 102), xlim = c(1, 3.2)) 
dotprint <- dotprint + scale_fill_manual(name = "ASD cases who are:", 
                                     values = c("black", "white"), 
                                     labels = c("Not accessing services",
                                                "Accessing services")) 
dotprint <- dotprint + guides(colour = guide_legend(keywidth = 1.1, 
                                keyheight = 1.1, 
                                override.aes = list(alpha = 1, size = 3))) 
dotprint <- dotprint + theme(legend.position=c(.75, .25)) 
dotprint <- dotprint + theme(legend.text = element_text(size = 10)) 
dotprint <- dotprint + theme(legend.title = element_text(size = 10)) 
dotprint <- dotprint + theme(legend.background = element_rect(fill = "gray90", 
                                          size=.3, 
                                          linetype="dotted"))
# lines
dotprint <- dotprint + geom_segment(aes(x = .6, xend = 2.4, y = 100, yend = 100), 
                              lty = 3, lwd = .5, colour = "black") 
dotprint <- dotprint + geom_segment(aes(x = .6, xend = 1.3, y = 30, yend = 30), 
                              lty = 3, lwd = .5, colour = "black") 
dotprint <- dotprint + geom_segment(aes(x = 1.3, xend = 1.7, y = 30, yend = 60), 
                              lty = 3, lwd = .5, colour = "black") 
dotprint <- dotprint + geom_segment(aes(x = 1.7, xend = 2.4, y = 60, yend = 60), 
                              lty = 3, lwd = .5, colour = "black")
dotprint <- dotprint + annotate("text", 
                            x = 2.5, y = 97, size = 4, hjust = 0, 
                            label = "Estimates of prevalence based\non population sampling will remain\nstable over time if true prevalence\nis stable.")  
dotprint <- dotprint + annotate("text", 
                            x = 2.5, y = 60, size = 4, hjust = 0, 
                            label = "Estimates of prevalence based\non individuals accessing services\ncan create an illusion of an\nincrease in prevalence over time,\nyet still underestimate prevalence\nat both time points.") 
dotprint

Alluvial Plot

Read data:

# migration <- read_rds("migration.rds")
migration <- read_rds("https://github.com/reisanar/datasets/blob/master/migration.rds?raw=true")
cannot open compressed file 'https://github.com/reisanar/datasets/blob/master/migration.rds?raw=true', probable reason 'No such file or directory'Error in gzfile(file, "rb") : cannot open the connection
myurl <- "https://github.com/reisanar/datasets/blob/master/migration.rds?raw=true"
temp <- tempfile() # create a tempfile
download.file(myurl, temp) # download to disk
trying URL 'https://github.com/reisanar/datasets/blob/master/migration.rds?raw=true'
Content type 'application/octet-stream' length 6185196 bytes (5.9 MB)
==================================================
downloaded 5.9 MB
migration <- readRDS(temp) # read the tempfile
unlink(temp) # Deletes tempfile

Check

head(migration)
colnames(migration) %>% head()
[1] "gender"       "year"         "code"         "country_dest" "afghanistan"  "albania"     
colnames(migration) %>% tail()
[1] "viet_nam"                  "wallis_and_futuna_islands" "western_sahara"           
[4] "yemen"                     "zambia"                    "zimbabwe"                 

G7 countries:

g7 <- c("canada", "france", "germany", "italy", "japan", "united_kingdom", "united_states_of_america")
migration_2019 <- migration %>%
  pivot_longer(cols = -c(1:4), names_to = "country_orig", values_to = "n_migrants") %>%
  filter(year == 2019) %>%
  filter_at(c("country_orig", "country_dest"), ~.x %in% g7) %>%
  mutate_at(c("country_orig", "country_dest"), ~case_when(.x == "united_kingdom" ~ "uk", .x == "united_states_of_america" ~ "usa", TRUE ~ .x)) %>%
  group_by(country_orig, country_dest) %>%
  summarise(n_migrants = sum(n_migrants))
migration_2019 %>% head(4)
library(ggalluvial)
library(scales)

Sankey Diagram (Alluvial Plot)

ggplot(migration_2019,
       aes(y = n_migrants, axis1 = country_orig, axis2 = country_dest)) +
  geom_alluvium(aes(fill = country_orig)) +
  geom_stratum(width = 1/12, fill = "black", color = "grey") +
  geom_label(stat = "stratum", infer.label = TRUE) +
  scale_x_discrete(limits = c("Origin", "Destination"), expand = c(.05, .05)) +
  scale_fill_brewer(type = "qual", palette = "Set1") +
  guides(fill = FALSE) +
  labs(y = "", title = "G7 Cross-Country Migration 2019") 

head(migration)

Another Example

data(majors)

Check

majors

Adjust data type:

majors$curriculum <- factor(majors$curriculum)

Quick plot:

ggplot(data = majors, 
       aes(x = semester, stratum = curriculum, alluvium = student, 
           fill = curriculum, label = curriculum)) + 
  geom_stratum() + 
  geom_flow() + 
  theme(legend.position = "bottom") + 
  labs(title = "Student curricula across several semesters")

The stratum heights y are unspecified, so each row is given unit height. This example demonstrates one way ggalluvial handles missing data. The alternative is to set the parameter na.rm to TRUE. Missing data handling (specifically, the order of the strata) also depends on whether the stratum variable is character or factor/numeric.

ggplot(data = majors, 
       aes(x = semester, stratum = curriculum, alluvium = student, 
           fill = curriculum, label = curriculum)) + 
  geom_stratum(na.rm = TRUE) + 
  geom_flow(na.rm = TRUE) + 
  theme(legend.position = "bottom") + 
  labs(title = "Student curricula across several semesters")

LS0tCnRpdGxlOiAiSW1wcm92aW5nIFZpc3VhbHMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKIyMgTWF0ZXJpYWxzIGJ5IEFsaXNvbiBQcmVzbWFuZXMgSGlsbAoKPiBUYWtlIEEgU2FkIFBsb3QgJiBNYWtlIEl0IEJldHRlcgoKPGh0dHBzOi8vYXByZXNoaWxsLmdpdGh1Yi5pby9vaHN1LWJpb2RhdGF2aXMvc2xpZGVzLmh0bWwjMT4KClF1aWNrIGV4YW1wbGU6IAoKCmBgYHtyIHRpYmJsZXNldCwgZWNobyA9IFRSVUV9CnNldC5zZWVkKDEwMDApCmFzZHBvcCA8LSB0aWJibGU6OnRpYmJsZSgKICB0aW1lMSA9IHNhbXBsZSgxOjEwMCwgMTAwLCByZXBsYWNlID0gRiksIAogIHRpbWUyID0gdGltZTEpICU+JSAKICB0aWR5cjo6Z2F0aGVyKHgsIHksIHRpbWUxOnRpbWUyLCBmYWN0b3Jfa2V5ID0gVFJVRSkgIAphc2Rwb3AKYGBgCgpTb21lIGVkaXRzOiAKCmBgYHtyIHRpYmJsZWZhY3RvciwgZWNobyA9IFRSVUV9CmFzZHBvcCA8LSBhc2Rwb3AgJT4lIAogIG11dGF0ZShzZXJ2aWNlcyA9IGFzLmZhY3RvcihjYXNlX3doZW4oCiAgICB4ID09ICJ0aW1lMSIgJiB5IDw9IDMwIH4gMSwgCiAgICB4ID09ICJ0aW1lMSIgJiB5ID4gMzAgfiAwLCAKICAgIHggPT0gInRpbWUyIiAmIHkgPD0gNjAgfiAxLCAKICAgIFRSVUUgfiAwCiAgICApKSkKYXNkcG9wCmBgYAoKCiMjIyBGaXJzdCBzaG90CgpgYGB7ciBiYXIxfQpiYXIxIDwtIGdncGxvdChhc2Rwb3AsIGFlcyh4LCBmaWxsID0gc2VydmljZXMpKSAKYmFyMSA8LSBiYXIxICsgZ2VvbV9iYXIod2lkdGggPSAuNikKYmFyMQpgYGAKClNpbXBsZSwgcmlnaHQ/IAoKLSBTZXQgY29sb3JzIG9mIHlvdXIgcHJlZmVyZW5jZToKCmBgYHtyfQpsaWJyYXJ5KHdlc2FuZGVyc29uKQpmZiA8LSB3ZXNfcGFsZXR0ZSgiRmFudGFzdGljRm94MSIpW2MoMjozKV0KYmFyMiA8LSBiYXIxICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZmYpCmJhcjIKYGBgCgotIENsZWFuIHVwIGEgYml0OiBjaGFuZ2UgYmFja2dyb3VuZCwgYWRqdXN0IGF4ZXMsIGFkanVzdCB0aWNrcywgZXRjCgpgYGB7ciBiYXIzfQpiYXIzIDwtIGJhcjIgKyBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiIiwgbGFiZWxzID0gYygiVGltZSAxIiwgIlRpbWUgMiIpKQpiYXIzIDwtIGJhcjMgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYyguMDIsIDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJBU0QgQ2FzZXMgcGVyIDEwLDAwMCIpIApiYXIzIDwtIGJhcjMgKyB0aGVtZV9idygpIApiYXIzIDwtIGJhcjMgKyB0aGVtZShheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpIApiYXIzIDwtIGJhcjMgKyB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSAKYmFyMyA8LSBiYXIzICsgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpIApiYXIzIDwtIGJhcjMgKyB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSAgCmJhcjMgPC0gYmFyMyArIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgIApiYXIzIDwtIGJhcjMgKyB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCkpIApiYXIzIDwtIGJhcjMgKyB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQpiYXIzCmBgYAoKCi0gQW5ub3RhdGUgYXMgbmVlZGVkIHRvIGRlbGl2ZXIgdGhlIG1lc3NhZ2U6IGRvIHdlIG5lZWQgYSBsZWdlbmQ/CgpgYGB7ciBiYXI0fQpiYXI0IDwtIGJhcjMgKyBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsID0gIkFjY2Vzc2luZyBcblNlcnZpY2VzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHggPSAyLCB5ID0gMzAsIHNpemUgPSA0LCBjb2xvciA9ICJ3aGl0ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIikgCmJhcjQgPC0gYmFyNCArIGFubm90YXRlKCJ0ZXh0IiwgbGFiZWwgPSAiTm90IFxuQWNjZXNzaW5nIFxuU2VydmljZXMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgeCA9IDIsIHkgPSA4MCwgc2l6ZSA9IDQsIGNvbG9yID0gIndoaXRlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGZvbnRmYWNlID0gImJvbGQiKSAKYmFyNCA8LSBiYXI0ICsgZ3VpZGVzKGZpbGwgPSBGQUxTRSkKYmFyNApgYGAKCgotIEtlZXAgZGVsaXZlcmluZyB0aGUgbWVzc2FnZQoKYGBge3IgYmFyNX0KIyBhZGQgdGhlIHRvcCBob3Jpem9udGFsIGxpbmUgZm9yIHBvcHVsYXRpb24gcHJldmFsZW5jZQpiYXI1IDwtIGJhcjQgKyBnZW9tX3NlZ21lbnQoYWVzKHggPSAuNiwgeGVuZCA9IDIuNDUsIHkgPSAxMDAsIHllbmQgPSAxMDApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC4zLCBjb2xvdXIgPSAiYmxhY2siKQpiYXI1CmBgYAoKCmBgYHtyIGJhcjZ9CmJhcjYgPC0gYmFyNSArIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLCAxMDIpLCB4bGltID0gYygxLCAzLjIpKSAKYmFyNiA8LSBiYXI2ICsgYW5ub3RhdGUoInRleHQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgeCA9IDIuNSwgeSA9IDk3LCBzaXplID0gNCwgaGp1c3QgPSAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAiRXN0aW1hdGVzIG9mIHByZXZhbGVuY2UgYmFzZWRcbm9uIHBvcHVsYXRpb24gc2FtcGxpbmcgd2lsbCByZW1haW5cbnN0YWJsZSBvdmVyIHRpbWUgaWYgdHJ1ZSBwcmV2YWxlbmNlXG5pcyBzdGFibGUuIikgCmJhcjYKYGBgCgotIEFkZGluZyBzZWdtZW50czogZW1waGFzaXplIGNoYW5nZQoKYGBge3IgYmFyN30KIyBhZGQgc2VnbWVudHMgdG8gdHJhY2sgc2FtcGxlIHByZXZhbGVuY2UKYmFyNyA8LSBiYXI2ICsgZ2VvbV9zZWdtZW50KGFlcyh4ID0gLjYsIHhlbmQgPSAxLjMsIHkgPSAzMCwgeWVuZCA9IDMwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsdHkgPSAzLCBsd2QgPSAuNSwgY29sb3VyID0gZmZbMl0pIApiYXI3IDwtIGJhcjcgKyBnZW9tX3NlZ21lbnQoYWVzKHggPSAxLjMsIHhlbmQgPSAxLjcsIHkgPSAzMCwgeWVuZCA9IDYwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsdHkgPSAzLCBsd2QgPSAuNSwgY29sb3VyID0gZmZbMl0pIApiYXI3IDwtIGJhcjcgKyBnZW9tX3NlZ21lbnQoYWVzKHggPSAxLjcsIHhlbmQgPSAyLjQ1LCB5ID0gNjAsIHllbmQgPSA2MCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbHR5ID0gMywgbHdkID0gLjUsIGNvbG91ciA9IGZmWzJdKQpiYXI3CmBgYAoKSG93IHdlcmUgdGhvc2Ugc3BlY2lmaWMgbnVtYmVycyBvYnRhaW5lZD8KCmBgYHtyfQphc2Rwb3AgJT4lIAogIGdyb3VwX2J5KHNlcnZpY2VzKSAlPiUgCiAgc3VtbWFyaXplKG1pbmltdW0gPSBtaW4oeSksIAogICAgICAgICAgICBtYXhpbXVtID0gbWF4KHkpKQpgYGAKCi0gTmV3IGFubm90YXRpb24KCmBgYHtyIGJhcjh9CmJhcjggPC0gYmFyNyArIGFubm90YXRlKCJ0ZXh0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHggPSAyLjUsIHkgPSA2MCwgc2l6ZSA9IDQsIGhqdXN0ID0gMCwgCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIkVzdGltYXRlcyBvZiBwcmV2YWxlbmNlIGJhc2VkXG5vbiBpbmRpdmlkdWFscyBhY2Nlc3Npbmcgc2VydmljZXNcbmNhbiBjcmVhdGUgYW4gaWxsdXNpb24gb2YgYW5cbmluY3JlYXNlIGluIHByZXZhbGVuY2Ugb3ZlciB0aW1lLFxueWV0IHN0aWxsIHVuZGVyZXN0aW1hdGUgcHJldmFsZW5jZVxuYXQgYm90aCB0aW1lIHBvaW50cy4iKQpiYXI4CmBgYAoKCi0gQ2FuIHdlIHZpc3VhbHplIGhvdyBtYW55IGFjdHVhbCBvYnNlcnZhdGlvbnMgYXJlIGluIGVhY2ggY2F0ZWdvcnk/CgpgYGB7ciBkb3QxfQpzZXQuc2VlZCgyMDE4KQpkb3QgPC0gZ2dwbG90KGFzZHBvcCwgYWVzKHgpKQpkb3QgPC0gZG90ICsgZ2VvbV9qaXR0ZXIoYWVzKHkgPSB5LCBjb2xvdXIgPSBzZXJ2aWNlcyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gLjI1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAuNzUsIHNpemUgPSAyKSAKZG90IDwtIGRvdCArIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICIiLCBsYWJlbHMgPSBjKCJUaW1lIDEiLCAiVGltZSAyIikpCmRvdCA8LSBkb3QgKyBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJBU0QgQ2FzZXMgcGVyIDEwLDAwMCIpIApkb3QgPC0gZG90ICsgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChrZXloZWlnaHQgPSAxLjUpKQpkb3QKYGBgCgoKLSBMaWtlIGJlZm9yZSwgY29uc2lkZXIgYSBjbGVhbmVyIGxvb2s6CgpgYGB7ciBkb3QyfQpkb3RzZWcgPC0gZG90ICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBmZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTm90IGFjY2Vzc2luZyBcbnNlcnZpY2VzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBY2Nlc3NpbmcgXG5zZXJ2aWNlcyIpKSAKZG90c2VnIDwtIGRvdHNlZyArIGFubm90YXRlKCJ0ZXh0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHggPSAxLjIsIHkgPSAxMDIsIHNpemUgPSA0LCBoanVzdCA9IDAsIAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJUcnVlIEFTRCBQcmV2YWxlbmNlIikKZG90c2VnIDwtIGRvdHNlZyArIGdlb21fc2VnbWVudChhZXMoeCA9IC42LCB4ZW5kID0gMi40LCB5ID0gMTAwLCB5ZW5kID0gMTAwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC41LCBjb2xvdXIgPSAiYmxhY2siKSAKZG90c2VnIDwtIGRvdHNlZyArIGdlb21fc2VnbWVudChhZXMoeCA9IC42LCB4ZW5kID0gMS4zLCB5ID0gMzAsIHllbmQgPSAzMCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsdHkgPSAzLCBsd2QgPSAuNSwgY29sb3VyID0gZmZbMl0pIApkb3RzZWcgPC0gZG90c2VnICsgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMS4zLCB4ZW5kID0gMS43LCB5ID0gMzAsIHllbmQgPSA2MCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsdHkgPSAzLCBsd2QgPSAuNSwgY29sb3VyID0gZmZbMl0pIApkb3RzZWcgPC0gZG90c2VnICsgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMS43LCB4ZW5kID0gMi40LCB5ID0gNjAsIHllbmQgPSA2MCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsdHkgPSAzLCBsd2QgPSAuNSwgY29sb3VyID0gZmZbMl0pCmRvdHNlZyA8LSBkb3RzZWcgKyB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSAKZG90c2VnCmBgYAoKCgotIERlbGl2ZXIgdGhlIG1lc3NhZ2U6IGhpZ2hsaWdodCBvYnNlcnZhdGlvbnMKCgpgYGB7ciBkb3QzfQpzZXQuc2VlZCgyMDE4KQpkb3Rjb2wgPC0gZ2dwbG90KGFzZHBvcCwgYWVzKHgpKQpkb3Rjb2wgPC0gZG90Y29sICsgZ2VvbV9iYXIoZmlsbCA9ICJ3aGl0ZSIsIHdpZHRoID0gLjYpCmRvdGNvbCA8LSBkb3Rjb2wgKyBnZW9tX2ppdHRlcihhZXMoeSA9IHksIGNvbG91ciA9IHNlcnZpY2VzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAuMjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlaWdodCA9IDApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IC43NSwgc2l6ZSA9IDIpIApkb3Rjb2wgPC0gZG90Y29sICsgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIiIsIGxhYmVscyA9IGMoIlRpbWUgMSIsICJUaW1lIDIiKSkKZG90Y29sIDwtIGRvdGNvbCArIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIkFTRCBDYXNlcyBwZXIgMTAsMDAwIikgCmRvdGNvbCA8LSBkb3Rjb2wgKyBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGZmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJOb3QgYWNjZXNzaW5nIFxuc2VydmljZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFjY2Vzc2luZyBcbnNlcnZpY2VzIikpIApkb3Rjb2wgPC0gZG90Y29sICsgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChrZXloZWlnaHQgPSAxLjUpKQpkb3Rjb2wgPC0gZG90Y29sICsgYW5ub3RhdGUoInRleHQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgeCA9IDEuMiwgeSA9IDEwMiwgc2l6ZSA9IDQsIGhqdXN0ID0gMCwgCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIlRydWUgQVNEIFByZXZhbGVuY2UiKQpkb3Rjb2wgPC0gZG90Y29sICsgZ2VvbV9zZWdtZW50KGFlcyh4ID0gLjYsIHhlbmQgPSAyLjQsIHkgPSAxMDAsIHllbmQgPSAxMDApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbHR5ID0gMywgbHdkID0gLjUsIGNvbG91ciA9ICJibGFjayIpIApkb3Rjb2wgPC0gZG90Y29sICsgZ2VvbV9zZWdtZW50KGFlcyh4ID0gLjYsIHhlbmQgPSAxLjMsIHkgPSAzMCwgeWVuZCA9IDMwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC41LCBjb2xvdXIgPSBmZlsyXSkgCmRvdGNvbCA8LSBkb3Rjb2wgKyBnZW9tX3NlZ21lbnQoYWVzKHggPSAxLjMsIHhlbmQgPSAxLjcsIHkgPSAzMCwgeWVuZCA9IDYwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC41LCBjb2xvdXIgPSBmZlsyXSkgCmRvdGNvbCA8LSBkb3Rjb2wgKyBnZW9tX3NlZ21lbnQoYWVzKHggPSAxLjcsIHhlbmQgPSAyLjQsIHkgPSA2MCwgeWVuZCA9IDYwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC41LCBjb2xvdXIgPSBmZlsyXSkKZG90Y29sIDwtIGRvdGNvbCArIHRoZW1lKGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpIApkb3Rjb2wgPC0gZG90Y29sICsgdGhlbWUobGVnZW5kLmtleT1lbGVtZW50X2JsYW5rKCkpIApkb3Rjb2wKYGBgCgoKCi0gRGF0YS9JbmsgcmF0aW86IGNsZWFuZXIgbG9vawoKCmBgYHtyIGRvdDR9CnNldC5zZWVkKDIwMTgpCmRvdGJ3IDwtIGdncGxvdChhc2Rwb3AsIGFlcyh4LCB5KSkKZG90YncgPC0gZG90YncgKyBnZW9tX2ppdHRlcihhZXMoY29sb3VyID0gc2VydmljZXMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IC4yNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0ID0gMCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gLjc1LCBzaXplID0gMikgCmRvdGJ3IDwtIGRvdGJ3ICsgc2NhbGVfeF9kaXNjcmV0ZShuYW1lID0gIiIsIGxhYmVscyA9IGMoIlRpbWUgMSIsICJUaW1lIDIiKSkKZG90YncgPC0gZG90YncgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYyguMDIsIDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIkFTRCBDYXNlcyBwZXIgMTAsMDAwIikgCmRvdGJ3IDwtIGRvdGJ3ICsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBmZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTm90IGFjY2Vzc2luZyBcbnNlcnZpY2VzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBY2Nlc3NpbmcgXG5zZXJ2aWNlcyIpKSAKZG90YncgPC0gZG90YncgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKGtleWhlaWdodCA9IDEuNSkpCmRvdGJ3IDwtIGRvdGJ3ICsgdGhlbWVfYncoKSAKZG90YncgPC0gZG90YncgKyB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSAKZG90YncgPC0gZG90YncgKyB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpIApkb3RidyA8LSBkb3RidyArIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpIApkb3RidyA8LSBkb3RidyArIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSAKZG90YncgPC0gZG90YncgKyB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKZG90YncgPC0gZG90YncgKyB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImdyYXk4MCIpKQpkb3RidwpgYGAKCgoKLSBIb3cgYXJlIGBUaW1lIDFgIGFuZCBgVGltZSAyYCBjaGFyYWN0ZXJpemVkPwoKCgpgYGB7ciBkb3Q1fQpzZXQuc2VlZCgyMDE4KQpkb3RsZWcgPC0gZ2dwbG90KGFzZHBvcCwgYWVzKHgsIHkpKQpkb3RsZWcgPC0gZG90bGVnICsgZ2VvbV9qaXR0ZXIoYWVzKGNvbG91ciA9IHNlcnZpY2VzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAuMjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlaWdodCA9IDApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IC43NSwgc2l6ZSA9IDIpIApkb3RsZWcgPC0gZG90bGVnICsgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBjKDAsIDAuNiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlRpbWUgMTpcblBvb3IgU2VydmljZSBBY2Nlc3MiLCAiVGltZSAyOlxuQmV0dGVyIFNlcnZpY2UgQWNjZXNzIikpCmRvdGxlZyA8LSBkb3RsZWcgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYyguMDIsIDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiQVNEIENhc2VzIHBlciAxMCwwMDAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLCAxMDAsIGJ5ID0gMjApKSAKZG90bGVnIDwtIGRvdGxlZyArIHRoZW1lX2J3KCkgCmRvdGxlZyA8LSBkb3RsZWcgKyB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSAKZG90bGVnIDwtIGRvdGxlZyArIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgCmRvdGxlZyA8LSBkb3RsZWcgKyB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSAKZG90bGVnIDwtIGRvdGxlZyArIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSAKZG90bGVnIDwtIGRvdGxlZyArIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpkb3RsZWcgPC0gZG90bGVnICsgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsIDEwMiksIHhsaW0gPSBjKDEsIDMuMikpIApkb3RsZWcgPC0gZG90bGVnICsgc2NhbGVfY29sb3VyX21hbnVhbChuYW1lID0gIkFTRCBjYXNlcyB3aG8gYXJlOiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gZmYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTm90IGFjY2Vzc2luZyBzZXJ2aWNlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBY2Nlc3Npbmcgc2VydmljZXMiKSkgCmRvdGxlZyA8LSBkb3RsZWcgKyBndWlkZXMoY29sb3VyID0gZ3VpZGVfbGVnZW5kKGtleXdpZHRoID0gMS4xLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXloZWlnaHQgPSAxLjEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG92ZXJyaWRlLmFlcyA9IGxpc3QoYWxwaGEgPSAxLCBzaXplID0gMykpKSAKZG90bGVnIDwtIGRvdGxlZyArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj1jKC43NSwgLjI1KSkgCmRvdGxlZyA8LSBkb3RsZWcgKyB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSAKZG90bGVnIDwtIGRvdGxlZyArIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSAKZG90bGVnIDwtIGRvdGxlZyArIHRoZW1lKGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiZ3JheTkwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemU9LjMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5ldHlwZT0iZG90dGVkIikpCmRvdGxlZwpgYGAKCgoKLSBCcmluZyBiYWNrIHRoZSBzZWdtZW50cyB0byBoaWdobGlnaHQgc2xvcGU6IAoKYGBge3IgZG90Nn0KIyBsaW5lcwpkb3RsaW5lIDwtIGRvdGxlZyArIGdlb21fc2VnbWVudChhZXMoeCA9IC42LCB4ZW5kID0gMi40LCB5ID0gMTAwLCB5ZW5kID0gMTAwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC41LCBjb2xvdXIgPSAiYmxhY2siKSAKZG90bGluZSA8LSBkb3RsaW5lICsgZ2VvbV9zZWdtZW50KGFlcyh4ID0gLjYsIHhlbmQgPSAxLjMsIHkgPSAzMCwgeWVuZCA9IDMwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC41LCBjb2xvdXIgPSBmZlsyXSkgCmRvdGxpbmUgPC0gZG90bGluZSArIGdlb21fc2VnbWVudChhZXMoeCA9IDEuMywgeGVuZCA9IDEuNywgeSA9IDMwLCB5ZW5kID0gNjApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbHR5ID0gMywgbHdkID0gLjUsIGNvbG91ciA9IGZmWzJdKSAKZG90bGluZSA8LSBkb3RsaW5lICsgZ2VvbV9zZWdtZW50KGFlcyh4ID0gMS43LCB4ZW5kID0gMi40LCB5ID0gNjAsIHllbmQgPSA2MCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsdHkgPSAzLCBsd2QgPSAuNSwgY29sb3VyID0gZmZbMl0pCmRvdGxpbmUKYGBgCgoKLSBBbm5vdGF0ZQoKYGBge3IgZG90N30KZG90YW5uIDwtIGRvdGxpbmUgKyBhbm5vdGF0ZSgidGV4dCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IDIuNSwgeSA9IDk3LCBzaXplID0gNCwgaGp1c3QgPSAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gIkVzdGltYXRlcyBvZiBwcmV2YWxlbmNlIGJhc2VkXG5vbiBwb3B1bGF0aW9uIHNhbXBsaW5nIHdpbGwgcmVtYWluXG5zdGFibGUgb3ZlciB0aW1lIGlmIHRydWUgcHJldmFsZW5jZVxuaXMgc3RhYmxlLiIpICAKZG90YW5uIDwtIGRvdGFubiArIGFubm90YXRlKCJ0ZXh0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0gMi41LCB5ID0gNjAsIHNpemUgPSA0LCBoanVzdCA9IDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAiRXN0aW1hdGVzIG9mIHByZXZhbGVuY2UgYmFzZWRcbm9uIGluZGl2aWR1YWxzIGFjY2Vzc2luZyBzZXJ2aWNlc1xuY2FuIGNyZWF0ZSBhbiBpbGx1c2lvbiBvZiBhblxuaW5jcmVhc2UgaW4gcHJldmFsZW5jZSBvdmVyIHRpbWUsXG55ZXQgc3RpbGwgdW5kZXJlc3RpbWF0ZSBwcmV2YWxlbmNlXG5hdCBib3RoIHRpbWUgcG9pbnRzLiIpIApkb3Rhbm4KYGBgCgotIFB1dHRpbmcgbWFueSBwaWVjZXMgdG9nZXRoZXI6CgpgYGB7ciBkb3Q4fQpzZXQuc2VlZCgyMDE4KQpkb3RwcmludCA8LSBnZ3Bsb3QoYXNkcG9wLCBhZXMoeCwgeSkpCmRvdHByaW50IDwtIGRvdHByaW50ICsgZ2VvbV9qaXR0ZXIoYWVzKGZpbGwgPSBzZXJ2aWNlcyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoPS4yNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQgPSAwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwY2ggPSAyMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvdXIgPSAiYmxhY2siLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMikgCmRvdHByaW50IDwtIGRvdHByaW50ICsgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQgPSBjKDAsIDAuNiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlRpbWUgMTpcblBvb3IgU2VydmljZSBBY2Nlc3MiLCAiVGltZSAyOlxuQmV0dGVyIFNlcnZpY2UgQWNjZXNzIikpCmRvdHByaW50IDwtIGRvdHByaW50ICsgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoLjAyLCAwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIkFTRCBDYXNlcyBwZXIgMTAsMDAwIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMTAwLCBieSA9IDIwKSkgCmRvdHByaW50IDwtIGRvdHByaW50ICsgdGhlbWVfYncoKSAKZG90cHJpbnQgPC0gZG90cHJpbnQgKyB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSAKZG90cHJpbnQgPC0gZG90cHJpbnQgKyB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpIApkb3RwcmludCA8LSBkb3RwcmludCArIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpIApkb3RwcmludCA8LSBkb3RwcmludCArIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSAKZG90cHJpbnQgPC0gZG90cHJpbnQgKyB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKZG90cHJpbnQgPC0gZG90cHJpbnQgKyBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwgMTAyKSwgeGxpbSA9IGMoMSwgMy4yKSkgCmRvdHByaW50IDwtIGRvdHByaW50ICsgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICJBU0QgY2FzZXMgd2hvIGFyZToiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGMoImJsYWNrIiwgIndoaXRlIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiTm90IGFjY2Vzc2luZyBzZXJ2aWNlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBY2Nlc3Npbmcgc2VydmljZXMiKSkgCmRvdHByaW50IDwtIGRvdHByaW50ICsgZ3VpZGVzKGNvbG91ciA9IGd1aWRlX2xlZ2VuZChrZXl3aWR0aCA9IDEuMSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5aGVpZ2h0ID0gMS4xLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdmVycmlkZS5hZXMgPSBsaXN0KGFscGhhID0gMSwgc2l6ZSA9IDMpKSkgCmRvdHByaW50IDwtIGRvdHByaW50ICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoLjc1LCAuMjUpKSAKZG90cHJpbnQgPC0gZG90cHJpbnQgKyB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSAKZG90cHJpbnQgPC0gZG90cHJpbnQgKyB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgCmRvdHByaW50IDwtIGRvdHByaW50ICsgdGhlbWUobGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJncmF5OTAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT0uMywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmV0eXBlPSJkb3R0ZWQiKSkKIyBsaW5lcwpkb3RwcmludCA8LSBkb3RwcmludCArIGdlb21fc2VnbWVudChhZXMoeCA9IC42LCB4ZW5kID0gMi40LCB5ID0gMTAwLCB5ZW5kID0gMTAwKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx0eSA9IDMsIGx3ZCA9IC41LCBjb2xvdXIgPSAiYmxhY2siKSAKZG90cHJpbnQgPC0gZG90cHJpbnQgKyBnZW9tX3NlZ21lbnQoYWVzKHggPSAuNiwgeGVuZCA9IDEuMywgeSA9IDMwLCB5ZW5kID0gMzApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbHR5ID0gMywgbHdkID0gLjUsIGNvbG91ciA9ICJibGFjayIpIApkb3RwcmludCA8LSBkb3RwcmludCArIGdlb21fc2VnbWVudChhZXMoeCA9IDEuMywgeGVuZCA9IDEuNywgeSA9IDMwLCB5ZW5kID0gNjApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbHR5ID0gMywgbHdkID0gLjUsIGNvbG91ciA9ICJibGFjayIpIApkb3RwcmludCA8LSBkb3RwcmludCArIGdlb21fc2VnbWVudChhZXMoeCA9IDEuNywgeGVuZCA9IDIuNCwgeSA9IDYwLCB5ZW5kID0gNjApLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbHR5ID0gMywgbHdkID0gLjUsIGNvbG91ciA9ICJibGFjayIpCmRvdHByaW50IDwtIGRvdHByaW50ICsgYW5ub3RhdGUoInRleHQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAyLjUsIHkgPSA5Nywgc2l6ZSA9IDQsIGhqdXN0ID0gMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJFc3RpbWF0ZXMgb2YgcHJldmFsZW5jZSBiYXNlZFxub24gcG9wdWxhdGlvbiBzYW1wbGluZyB3aWxsIHJlbWFpblxuc3RhYmxlIG92ZXIgdGltZSBpZiB0cnVlIHByZXZhbGVuY2VcbmlzIHN0YWJsZS4iKSAgCmRvdHByaW50IDwtIGRvdHByaW50ICsgYW5ub3RhdGUoInRleHQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAyLjUsIHkgPSA2MCwgc2l6ZSA9IDQsIGhqdXN0ID0gMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9ICJFc3RpbWF0ZXMgb2YgcHJldmFsZW5jZSBiYXNlZFxub24gaW5kaXZpZHVhbHMgYWNjZXNzaW5nIHNlcnZpY2VzXG5jYW4gY3JlYXRlIGFuIGlsbHVzaW9uIG9mIGFuXG5pbmNyZWFzZSBpbiBwcmV2YWxlbmNlIG92ZXIgdGltZSxcbnlldCBzdGlsbCB1bmRlcmVzdGltYXRlIHByZXZhbGVuY2VcbmF0IGJvdGggdGltZSBwb2ludHMuIikgCmRvdHByaW50CmBgYAoKCiMjIEFsbHV2aWFsIFBsb3QKClJlYWQgZGF0YToKCmBgYHtyfQojIG1pZ3JhdGlvbiA8LSByZWFkX3JkcygibWlncmF0aW9uLnJkcyIpCm1pZ3JhdGlvbiA8LSByZWFkX3JkcygiaHR0cHM6Ly9naXRodWIuY29tL3JlaXNhbmFyL2RhdGFzZXRzL2Jsb2IvbWFzdGVyL21pZ3JhdGlvbi5yZHM/cmF3PXRydWUiKQpgYGAKCmBgYHtyfQpteXVybCA8LSAiaHR0cHM6Ly9naXRodWIuY29tL3JlaXNhbmFyL2RhdGFzZXRzL2Jsb2IvbWFzdGVyL21pZ3JhdGlvbi5yZHM/cmF3PXRydWUiCnRlbXAgPC0gdGVtcGZpbGUoKSAjIGNyZWF0ZSBhIHRlbXBmaWxlCmRvd25sb2FkLmZpbGUobXl1cmwsIHRlbXApICMgZG93bmxvYWQgdG8gZGlzawptaWdyYXRpb24gPC0gcmVhZFJEUyh0ZW1wKSAjIHJlYWQgdGhlIHRlbXBmaWxlCnVubGluayh0ZW1wKSAjIERlbGV0ZXMgdGVtcGZpbGUKYGBgCgoKQ2hlY2sgCgpgYGB7cn0KaGVhZChtaWdyYXRpb24pCmNvbG5hbWVzKG1pZ3JhdGlvbikgJT4lIGhlYWQoKQpjb2xuYW1lcyhtaWdyYXRpb24pICU+JSB0YWlsKCkKYGBgCgpHNyBjb3VudHJpZXM6CgpgYGB7cn0KZzcgPC0gYygiY2FuYWRhIiwgImZyYW5jZSIsICJnZXJtYW55IiwgIml0YWx5IiwgImphcGFuIiwgInVuaXRlZF9raW5nZG9tIiwgInVuaXRlZF9zdGF0ZXNfb2ZfYW1lcmljYSIpCmBgYAoKYGBge3J9Cm1pZ3JhdGlvbl8yMDE5IDwtIG1pZ3JhdGlvbiAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IC1jKDE6NCksIG5hbWVzX3RvID0gImNvdW50cnlfb3JpZyIsIHZhbHVlc190byA9ICJuX21pZ3JhbnRzIikgJT4lCiAgZmlsdGVyKHllYXIgPT0gMjAxOSkgJT4lCiAgZmlsdGVyX2F0KGMoImNvdW50cnlfb3JpZyIsICJjb3VudHJ5X2Rlc3QiKSwgfi54ICVpbiUgZzcpICU+JQogIG11dGF0ZV9hdChjKCJjb3VudHJ5X29yaWciLCAiY291bnRyeV9kZXN0IiksIH5jYXNlX3doZW4oLnggPT0gInVuaXRlZF9raW5nZG9tIiB+ICJ1ayIsIC54ID09ICJ1bml0ZWRfc3RhdGVzX29mX2FtZXJpY2EiIH4gInVzYSIsIFRSVUUgfiAueCkpICU+JQogIGdyb3VwX2J5KGNvdW50cnlfb3JpZywgY291bnRyeV9kZXN0KSAlPiUKICBzdW1tYXJpc2Uobl9taWdyYW50cyA9IHN1bShuX21pZ3JhbnRzKSkKbWlncmF0aW9uXzIwMTkgJT4lIGhlYWQoNCkKYGBgCgpgYGB7cn0KbGlicmFyeShnZ2FsbHV2aWFsKQpsaWJyYXJ5KHNjYWxlcykKYGBgCgoKU2Fua2V5IERpYWdyYW0gKFtBbGx1dmlhbCBQbG90XShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9BbGx1dmlhbF9kaWFncmFtKSkKCgoKYGBge3J9CmdncGxvdChtaWdyYXRpb25fMjAxOSwKICAgICAgIGFlcyh5ID0gbl9taWdyYW50cywgYXhpczEgPSBjb3VudHJ5X29yaWcsIGF4aXMyID0gY291bnRyeV9kZXN0KSkgKwogIGdlb21fYWxsdXZpdW0oYWVzKGZpbGwgPSBjb3VudHJ5X29yaWcpKSArCiAgZ2VvbV9zdHJhdHVtKHdpZHRoID0gMS8xMiwgZmlsbCA9ICJibGFjayIsIGNvbG9yID0gImdyZXkiKSArCiAgZ2VvbV9sYWJlbChzdGF0ID0gInN0cmF0dW0iLCBpbmZlci5sYWJlbCA9IFRSVUUpICsKICBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IGMoIk9yaWdpbiIsICJEZXN0aW5hdGlvbiIpLCBleHBhbmQgPSBjKC4wNSwgLjA1KSkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHR5cGUgPSAicXVhbCIsIHBhbGV0dGUgPSAiU2V0MSIpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgbGFicyh5ID0gIiIsIHRpdGxlID0gIkc3IENyb3NzLUNvdW50cnkgTWlncmF0aW9uIDIwMTkiKSAKYGBgCgpgYGB7cn0KaGVhZChtaWdyYXRpb24pCmBgYAoKCi0gU2VlIG1vcmUgZXhhbXBsZXMgaGVyZTogCjxodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2dhbGx1dmlhbC92aWduZXR0ZXMvZ2dhbGx1dmlhbC5odG1sPgoKLSBIZXJlJ3MgYSBnb29kIGV4YW1wbGUgb24gdGhlIHR5cGUgb2YgcHJvYmxlbXMvcmVzdWx0cyBmb3Igd2hpY2ggdGhpcyB0eXBlIG9mIHBsb3QgY2FuIGJlIHZlcnkgdXNlZnVsOiAKPGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS9hbGx1dmlhbC1kaWFncmFtcy03ODNiYmJiZTAxOTU+CgotIFRoaXMgYmxvZ3Bvc3QgYWxzbyBkb2VzIGEgZ29vZCBqb2IgYXQgbW90aXZhdGluZyB0aGUgdXNlIG9mIGFsbHV2aWFsIGRpYWdyYW1zOgo8aHR0cHM6Ly93d3cudGhpbmtpbmdvbmRhdGEuY29tL2FsbHV2aWFsLWRpYWdyYW0vPgoKCiMjIyBBbm90aGVyIEV4YW1wbGUKCmBgYHtyfQpkYXRhKG1ham9ycykKYGBgCgpDaGVjayAKCmBgYHtyfQptYWpvcnMKYGBgCgpBZGp1c3QgZGF0YSB0eXBlOgoKYGBge3J9Cm1ham9ycyRjdXJyaWN1bHVtIDwtIGZhY3RvcihtYWpvcnMkY3VycmljdWx1bSkKYGBgCgpRdWljayBwbG90OiAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1ham9ycywgCiAgICAgICBhZXMoeCA9IHNlbWVzdGVyLCBzdHJhdHVtID0gY3VycmljdWx1bSwgYWxsdXZpdW0gPSBzdHVkZW50LCAKICAgICAgICAgICBmaWxsID0gY3VycmljdWx1bSwgbGFiZWwgPSBjdXJyaWN1bHVtKSkgKyAKICBnZW9tX3N0cmF0dW0oKSArIAogIGdlb21fZmxvdygpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsgCiAgbGFicyh0aXRsZSA9ICJTdHVkZW50IGN1cnJpY3VsYSBhY3Jvc3Mgc2V2ZXJhbCBzZW1lc3RlcnMiKQpgYGAKClRoZSBzdHJhdHVtIGhlaWdodHMgYHlgIGFyZSB1bnNwZWNpZmllZCwgc28gZWFjaCByb3cgaXMgZ2l2ZW4gdW5pdCBoZWlnaHQuIFRoaXMgZXhhbXBsZSBkZW1vbnN0cmF0ZXMgb25lIHdheSBgZ2dhbGx1dmlhbGAgaGFuZGxlcyBfbWlzc2luZyBkYXRhXy4gVGhlIGFsdGVybmF0aXZlIGlzIHRvIHNldCB0aGUgcGFyYW1ldGVyIGBuYS5ybWAgdG8gYFRSVUVgLiBNaXNzaW5nIGRhdGEgaGFuZGxpbmcgKHNwZWNpZmljYWxseSwgdGhlIG9yZGVyIG9mIHRoZSBzdHJhdGEpIGFsc28gZGVwZW5kcyBvbiB3aGV0aGVyIHRoZSBgc3RyYXR1bWAgdmFyaWFibGUgaXMgY2hhcmFjdGVyIG9yIGZhY3Rvci9udW1lcmljLgoKYGBge3J9CmdncGxvdChkYXRhID0gbWFqb3JzLCAKICAgICAgIGFlcyh4ID0gc2VtZXN0ZXIsIHN0cmF0dW0gPSBjdXJyaWN1bHVtLCBhbGx1dml1bSA9IHN0dWRlbnQsIAogICAgICAgICAgIGZpbGwgPSBjdXJyaWN1bHVtLCBsYWJlbCA9IGN1cnJpY3VsdW0pKSArIAogIGdlb21fc3RyYXR1bShuYS5ybSA9IFRSVUUpICsgCiAgZ2VvbV9mbG93KG5hLnJtID0gVFJVRSkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKyAKICBsYWJzKHRpdGxlID0gIlN0dWRlbnQgY3VycmljdWxhIGFjcm9zcyBzZXZlcmFsIHNlbWVzdGVycyIpCmBgYAoK